//
// Created by wangjie on 24-9-13.
//
#pragma once

#ifndef LFMT_FORMAT_HPP_
#define LFMT_FORMAT_HPP_

#include "utils.hpp"
#include "dragonbox/dragonbox.h"
#include <array>
#include <cstdio>
#include <string>
#include <cfloat>
#include <string_view>
#include <sys/_types/_size_t.h>
#include <tuple>

namespace lfmt {
namespace detail {

template <typename out_fct_wrap_t>
bool formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, bool arg) {
    return out(out_fct_wrap, options, arg ? "true" : "false");
}

template <typename out_fct_wrap_t, typename Arg>
std::enable_if_t<is_char_v<Arg>, bool>
formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, Arg arg) {
    return out(out_fct_wrap, options, std::string_view(&arg, 1));
}

template <typename out_fct_wrap_t, typename Arg>
std::enable_if_t<std::is_integral_v<std::decay_t<Arg>> && !is_char_v<Arg>, bool>
formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, Arg arg) {
    char buffer[33];
    int radix;
    switch (options.type) {
    case Type::bin:
        radix = 2;
        break;

    case Type::oct:
        radix = 8;
        break;

    case Type::dec:
    case Type::none:
        radix = 10;
        break;

    case Type::hex:
        radix = 16;
        break;

    default:
        return false;
    }

    auto s = itostr(arg, buffer, sizeof(buffer), radix, options.upper, options.sign);
    return out(out_fct_wrap, options, s);
}

template <typename out_fct_wrap_t, typename Arg>
std::enable_if_t<std::is_floating_point_v<std::decay_t<Arg>>, bool>
formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, Arg arg) {
    constexpr Arg max = std::is_same_v<std::decay_t<Arg>, float>
                            ? FLT_MAX
                            : (std::is_same_v<std::decay_t<Arg>, double> ? DBL_MAX : LDBL_MAX);
    constexpr auto exp10 = exp10array<20>();
    auto [significand, exponent, is_negative] = jkj::dragonbox::to_decimal(arg);
    int width = 0;
    for (size_t left = 0, right = exp10.size() - 1; left <= right;) {
        width = (left + right) / 2;
        if (exp10[width] > significand) {
            right = width - 1;
        } else if (width + 1 == exp10.size() || exp10[width + 1] > significand) {
            exponent += width;
            break;
        } else {
            left = width + 1;
        }
    }

    bool use_exp;
    char buffer[20 + 3 + 6];
    char *iter = buffer;

    if (options.type == Type::exp) {
        use_exp = true;
    } else if (options.type != Type::general && options.type != Type::none) {
        use_exp = false;
    } else {
        constexpr int exp_lower = -4, exp_upper = 16;
        use_exp = exponent < exp_lower ||
                  exponent >= (options.precision < 0 ? exp_upper : options.precision);
    }

    if (is_negative) {
        *iter++ = '-';
    } else if (options.sign != Sign::minus) {
        *iter++ = static_cast<char>(options.sign);
    }

    if (isnan(arg)) {
        memcpy(iter, "nan", 3);
        iter += 3;
    } else if (arg > max || arg < -max) {
        memcpy(iter, "inf", 3);
        iter += 3;
    } else if (use_exp) {
        if (options.precision >= 0) {
            int d = options.precision - width;
            significand = d >= 0 ? significand * exp10[d] : (significand / exp10[-d - 1] + 5) / 10;
        }
        if (significand < 10) {
            *iter++ = significand + '0';
        } else {
            auto temp = itostr(significand, iter + 1, sizeof(buffer) - (iter + 1 - buffer));
            *iter = *(iter + 1);
            *(iter + 1) = '.';
            iter = const_cast<char *>(temp.end());
        }
        *iter++ = options.upper ? 'E' : 'e';
        if (exponent < 0) {
            *iter++ = '-';
            exponent = -exponent;
        } else {
            *iter++ = '+';
        }
        if (exponent < 10) {
            *iter++ = '0';
            *iter++ = exponent + '0';
        } else if (exponent < 100) {
            *iter++ = exponent / 10 + '0';
            *iter++ = exponent % 10 + '0';
        } else {
            *iter++ = exponent / 100 + '0';
            *iter++ = exponent / 10 % 10 + '0';
            *iter++ = exponent % 10 + '0';
        }
    } else if (int len = exponent - width; len >= 0) {
        auto temp = itostr(significand, iter, std::size(buffer) - (iter - buffer));
        iter = const_cast<char *>(temp.end());
        std::memset(iter, '0', len);
        iter += len;
        if (options.precision >= 0) {
            *iter++ = '.';
            memset(iter, '0', options.precision);
            iter += options.precision;
        }
    } else if (exponent < 0) {
        std::memcpy(iter, "0.", 2);
        iter += 2;
        if (options.precision < 0) {
            std::memset(iter, '0', -(exponent + 1));
            iter += -(exponent + 1);
            auto temp = itostr(significand, iter, std::size(buffer) - (iter - buffer));
            iter = const_cast<char *>(temp.end());
        } else {
            if (-(exponent + 1) >= options.precision) {
                std::memset(iter, '0', options.precision);
                iter += options.precision;
            } else {
                int d = options.precision - -(exponent + 1) - (width + 1);
                significand =
                    d >= 0 ? significand * exp10[d] : (significand / exp10[-d - 1] + 5) / 10;
                if (significand >= exp10[options.precision]) {
                    *(iter - 2) = '1';
                    significand %= exp10[options.precision];
                    exponent -= 1;
                }
                std::memset(iter, '0', -(exponent + 1));
                iter += -(exponent + 1);
                auto temp = itostr(significand, iter, std::size(buffer) - (iter - buffer));
                iter = const_cast<char *>(temp.end());
            }
        }
    } else {
        uint64_t integer = significand / exp10[width - exponent];
        uint64_t fractional = significand % exp10[width - exponent] + exp10[width - exponent];
        if (options.precision >= 0) {
            int d = options.precision - (width - exponent);
            fractional = d >= 0 ? fractional * exp10[d] : (fractional / exp10[-d - 1] + 5) / 10;
            if (fractional >= 2 * exp10[options.precision]) {
                integer += 1;
            }
        }
        auto temp = itostr(integer, iter, std::size(buffer) - (iter - buffer));
        iter = const_cast<char *>(temp.end());
        temp = itostr(fractional, iter, std::size(buffer) - (iter - buffer));
        *iter = '.';
        iter = const_cast<char *>(temp.end());
    }

    return out(out_fct_wrap, options, std::string_view(buffer, iter - buffer));
}

template <typename out_fct_wrap_t, typename Arg>
std::enable_if_t<is_cstring_v<std::decay_t<Arg>>, bool>
formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, Arg arg) {
    return out(out_fct_wrap, options, arg);
}

template <typename out_fct_wrap_t>
bool formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, std::string_view arg) {
    return out(out_fct_wrap, options, arg);
}

template <typename out_fct_wrap_t>
bool formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, const std::string &arg) {
    return out(out_fct_wrap, options, arg);
}

template <typename out_fct_wrap_t, typename Arg>
std::enable_if_t<!is_cstring_v<std::decay_t<Arg>> && std::is_pointer_v<std::decay_t<Arg>>, bool>
formatter_to(out_fct_wrap_t &&out_fct_wrap, const Options &options, Arg arg) {
    char buffer[33];
    auto s =
        itostr(reinterpret_cast<uintptr_t>(arg), buffer + 3, sizeof(buffer) - 3, 16, options.upper);
    auto begin = const_cast<char *>(s.begin());
    *--begin = options.upper ? 'X' : 'x';
    *--begin = '0';
    if (options.sign != Sign::minus) {
        *--begin = static_cast<char>(options.sign);
    }
    return out(out_fct_wrap, options, std::string_view(begin, s.end() - begin));
}

template <size_t I, typename out_fct_wrap_t, typename... Args>
bool vformat_helper(size_t index,
                    out_fct_wrap_t &&out_fct_wrap,
                    const Context &context,
                    const std::tuple<Args...> &args) {
    if constexpr (I < sizeof...(Args)) {
        if (index == I) {
            auto options = context.parse();
            auto &arg = std::get<I>(args);

            if constexpr (std::is_class_v<std::decay_t<decltype(arg)>>) {
                return formatter_to(out_fct_wrap, options, arg);
            } else {
                switch (options.type) {
                case Type::bol:
                    if constexpr (std::is_pointer_v<std::decay_t<decltype(arg)>>) {
                        return formatter_to(
                            out_fct_wrap, options, reinterpret_cast<const char *>(arg));
                    } else if constexpr (static_cast_check_t<decltype(arg), bool>) {
                        return formatter_to(out_fct_wrap, options, static_cast<bool>(arg));
                    } else {
                        return false;
                    }

                case Type::chr:
                    if constexpr (static_cast_check_t<decltype(arg), char>) {
                        return formatter_to(out_fct_wrap, options, static_cast<char>(arg));
                    } else {
                        return false;
                    }

                case Type::bin:
                case Type::oct:
                case Type::dec:
                case Type::hex:
                    if constexpr (is_char_v<decltype(arg)>) {
                        if constexpr (std::is_signed_v<std::decay_t<decltype(arg)>>) {
                            return formatter_to(out_fct_wrap, options, static_cast<int>(arg));
                        } else {
                            return formatter_to(
                                out_fct_wrap, options, static_cast<unsigned int>(arg));
                        }
                    } else if constexpr (std::is_integral_v<std::decay_t<decltype(arg)>>) {
                        return formatter_to(out_fct_wrap, options, arg);
                    } else if constexpr (static_cast_check_t<decltype(arg), int>) {
                        return formatter_to(out_fct_wrap, options, static_cast<int>(arg));
                    } else {
                        return false;
                    }

                case Type::fixed:
                case Type::exp:
                case Type::general:
                    if constexpr (std::is_floating_point_v<std::decay_t<decltype(arg)>>) {
                        return formatter_to(out_fct_wrap, options, arg);
                    } else if constexpr (static_cast_check_t<decltype(arg), double>) {
                        return formatter_to(out_fct_wrap, options, static_cast<double>(arg));
                    } else {
                        return false;
                    }

                case Type::ptr:
                    if constexpr (std::is_pointer_v<std::decay_t<decltype(arg)>>) {
                        return formatter_to(out_fct_wrap, options, static_cast<const void *>(arg));
                    } else {
                        return false;
                    }

                case Type::none:
                    return formatter_to(out_fct_wrap, options, arg);

                default:
                    return false;
                }
            }
        }

        return vformat_helper<I + 1>(index, out_fct_wrap, context, args);
    } else {
        return false;
    }
}
} // namespace detail

template <typename out_fct_wrap_t, typename... Args>
size_t
vformat_to(out_fct_wrap_t &&out_fct_wrap, std::string_view fmt, const std::tuple<Args...> &args) {
    size_t count = 0;
    size_t index = 0;

    for (auto ch = fmt.begin(); ch != fmt.end();) {
        switch (*ch) {
        case '{':
            {
                const char *begin = ch, *end = nullptr, *colon = nullptr;

                while (++ch != fmt.end()) {
                    if (*ch == '{') {
                        break;
                    }
                    if (*ch == '}') {
                        end = ch + 1;
                        break;
                    }
                    if (*ch == ':' && colon == nullptr) {
                        colon = ch;
                    }
                }

                if (*ch == '}') {
                    detail::Context context = {std::string_view(begin, end - begin), colon};
                    if (isdigit(*(begin + 1))) {
                        count += detail::vformat_helper<0>(
                            std::strtol(begin + 1, nullptr, 10), out_fct_wrap, context, args);
                    } else {
                        count += detail::vformat_helper<0>(index++, out_fct_wrap, context, args);
                    }
                    ++ch;
                } else if (*ch == '{') {
                    out_fct_wrap('{');
                    ++ch;
                }

                break;
            }

        case '}':
            while (++ch != fmt.end()) {
                if (*ch == '}') {
                    out_fct_wrap('}');
                    break;
                }
            }
            break;

        default:
            out_fct_wrap(*ch++);
        }
    }

    return count;
}

template <typename out_fct_wrap_t, typename... Args>
size_t format_to(out_fct_wrap_t &&out_fct_wrap, std::string_view fmt, Args &&...args) {
    return vformat_to(out_fct_wrap, fmt, std::tuple<detail::type_choose_t<Args>...>(args...));
}

template <typename... Args> size_t vprint(std::string_view fmt, const std::tuple<Args...> &args) {
    return vformat_to(std::putchar, fmt, args);
}

template <typename... Args> size_t vprintln(std::string_view fmt, const std::tuple<Args...> &args) {
    auto count = vformat_to(std::putchar, fmt, args);
    std::putchar('\n');
    return count;
}

template <typename... Args> size_t print(std::string_view fmt, Args &&...args) {
    return vformat_to(std::putchar, fmt, std::tuple<detail::type_choose_t<Args>...>(args...));
}

template <typename... Args> size_t println(std::string_view fmt, Args &&...args) {
    auto count = vformat_to(std::putchar, fmt, std::tuple<detail::type_choose_t<Args>...>(args...));
    std::putchar('\n');
    return count;
}

template <typename... Args>
std::string_view
vcformat(void *buffer, size_t sz, std::string_view fmt, const std::tuple<Args...> &args) {
    auto begin = static_cast<char *>(buffer);
    auto end = begin + sz;
    auto iter = begin;
    auto out_fct_wrap = [end, &iter](char ch) {
        if (iter < end) {
            *iter++ = ch;
        }
    };
    vformat_to(out_fct_wrap, fmt, args);
    return std::string_view(begin, iter);
}

template <typename... Args>
std::string_view cformat(void *buffer, size_t sz, std::string_view fmt, Args &&...args) {
    auto begin = static_cast<char *>(buffer);
    auto end = begin + sz;
    auto iter = begin;
    auto out_fct_wrap = [end, &iter](char ch) {
        if (iter < end) {
            *iter++ = ch;
        }
    };
    vformat_to(out_fct_wrap, fmt, std::tuple<detail::type_choose_t<Args>...>(args...));
    return std::string_view(begin, iter);
}

template <typename... Args>
std::string vformat(std::string_view fmt, const std::tuple<Args...> &args) {
    std::string s;
    auto out_fct_wrap = [&s](char ch) { s.push_back(ch); };
    vformat_to(out_fct_wrap, fmt, args);
    return s;
}

template <typename... Args> std::string format(std::string_view fmt, Args &&...args) {
    std::string s;
    auto out_fct_wrap = [&s](char ch) { s.push_back(ch); };
    vformat_to(out_fct_wrap, fmt, std::tuple<detail::type_choose_t<Args>...>(args...));
    return s;
}

} // namespace lfmt

#endif // LFMT_FORMAT_HPP_
